#include "Editor.hpp"

Editor* Editor::instance = 0;

Editor::Editor(){
    instance = this;
    sf::err().rdbuf(cout.rdbuf());
	window = new sf::RenderWindow(sf::VideoMode(1280, 720), "Editor");
    window->setMouseCursorVisible(false);
    cursor = Arrow;
    hasFocus = true;

    marqueeMove = false;
    isMouseInMarquee = false;
    marqueeExists = false;
    hasPasted = false;
    hasCleared = false;
    hasCut = false;
    fpsCounter = new IntervalCounter(1000);
    
    editorSprites = new SpriteManager();
    editorSprites->loadRawSprites("editorsprites");

    gameSprites = new SpriteManager();
    gameSprites->loadRawSprites("sprites");
    gameDrawer = new GameDrawer();
    gameDrawer->spriteManager = *gameSprites;
    gameDrawer->setWindow(window);

    CM = 0;
	Map* m = 0;
	EditorRegionManager* rm = new EditorRegionManager();
	MapManager::loadRawMap("New.xml", &m, rm, gameSprites);
    addMapInstance(m, rm);
    camera = new Camera(0, window->getSize().x, window->getSize().y);

    undoTimer.setPeriods(4, 200, 55);
    
    //Setup UI
    trap(InterfaceEvent::LeftClick | InterfaceEvent::DoubleClick | InterfaceEvent::Custom);
    ui.parentHandler = this;
    ui.addElement(&toolPanel);

    //Menu buttons.
    brushButton.setRect(5,5,24,24);
    brushButton.setIcon(editorSprites->getSprite("brush_selected"));
    fillButton.setRect(34,5,24,24);
    fillButton.setIcon(editorSprites->getSprite("fill"));
    marqueeButton.setRect(63,5,24,24);
    marqueeButton.setIcon(editorSprites->getSprite("marquee"));
    moveButton.setRect(92,5,24,24);
    moveButton.setIcon(editorSprites->getSprite("move")); 
    saveButton.setRect(121,5,24,24);
    saveButton.setIcon(editorSprites->getSprite("save"));
    newMapButton.setRect(150,5,24,24);
    newMapButton.setIcon(editorSprites->getSprite("newmap"));
    openMapButton.setRect(179,5,24,24);
    openMapButton.setIcon(editorSprites->getSprite("openmap"));
    regionButton.setRect(5,29,24,24);
    regionButton.setIcon(editorSprites->getSprite("region"));
    
    
    //Add icons to toolpanel
    toolPanel.add(&brushButton);
    toolPanel.add(&fillButton);
    toolPanel.add(&marqueeButton);
    toolPanel.add(&moveButton);
    toolPanel.add(&saveButton);
    toolPanel.add(&newMapButton);
    toolPanel.add(&openMapButton);
    toolPanel.add(&regionButton);

    //Setup Info Panels
    toolPanel.addBottom(&mapPanel);
    toolPanel.addBottom(&clipboardPanel);
    
    toolPanel.addBottom(&regionPanel);
    
    spritePanel = new SpriteSelectorPanel(window->getSize().x, window->getSize().y, gameSprites);
    spritePanel->visible = false;
    ui.addElement(spritePanel);

    historyPanel.updateHistory(&CM->actionHistory);
    toolPanel.addBottom(&historyPanel);

    tilePanel.spriteManager = gameSprites;
    tilePanel.editorSprites = editorSprites;
    toolPanel.addBottom(&tilePanel);
    
    infoPanel.setSelectedSprite(gameSprites->getSprite((unsigned short)0));
    toolPanel.addBottom(&infoPanel);
    
    openFile = new FileDialog(window->getSize().x - 200, 0);
    openFile->visible = false;
    selectEditorInstance(CM);
    tabs.at(0)->closeButton->visible = false;

    selectedRegion = 0;
	regionResize = regionResizeX = regionResizeY = 0;
    regionWidth = regionHeight = regionX = regionY = 0;
    ui.addElement(&regionWindow);
    
    //Force resize to make sure all panels are properly sized and positioned.
    ui.onResize(window->getSize().x, window->getSize().y);

    scriptEngine.startup(this);
}

/** 
* Runs the editor, contains the main loop.
*/
void Editor::run(){
    while(window->isOpen()){
        frameTime = editorTimer.getElapsedTime().asMilliseconds();
        // Process events
        sf::Event event;
        while(window->pollEvent(event)){
            // Close window : exit
            if(event.type == sf::Event::Closed)
                window->close();
            else if(event.type == sf::Event::Resized){
                //Resize camera and tell UI that window has resized.
                camera->setWidth(window->getSize().x);
                camera->setHeight(window->getSize().y);
                window->setView(sf::View(sf::FloatRect(0, 0, camera->getWidth(), camera->getHeight())));
                ui.onResize(camera->getWidth(), camera->getHeight());
            }else if(event.type == sf::Event::LostFocus)
                hasFocus = false;
            else if(event.type == sf::Event::GainedFocus)
                hasFocus = true;
            else if(event.type == sf::Event::TextEntered){
                if(ui.hasKeyboard){
                    ui.keyEvent(event.text.unicode);
                    CM->map->name = mapPanel.name.text;
                    for(unsigned int i = 0; i < tabs.size(); i++){
                        tabs.at(i)->updateName();
                    }
                }
            }else if((event.type == sf::Event::MouseButtonReleased && event.mouseButton.button == sf::Mouse::Left) || event.type == sf::Event::MouseLeft){
                if(CM->marqueeTool.width < 0){
                    CM->marqueeTool.left += CM->marqueeTool.width;
                    CM->marqueeTool.width *= -1;
                }
                if(CM->marqueeTool.height < 0){
                    CM->marqueeTool.top += CM->marqueeTool.height;
                    CM->marqueeTool.height *= -1;
                }
                if(CM->marqueeTool.width == 0 || CM->marqueeTool.height == 0){
                    CM->marqueeTool.width = CM->marqueeTool.height = 0;
                }

                if(CM->selectedTool == EditorInstance::Move){
                    addAction(new MoveAction(CM->map, CM->moveSource, CM->marqueeTool));
                    selectTool(EditorInstance::Marquee);
                }
                marqueeMove = false;
            }else if(event.type == sf::Event::MouseWheelMoved){
                if(ui.hoverElement)
                    ui.wheelEvent(event.mouseWheel.delta);
                else if(camera->getScale() + event.mouseWheel.delta * 0.05 > 0.2 && camera->getScale() + event.mouseWheel.delta * 0.05 <= 1)
                    camera->setScale(camera->getScale() + event.mouseWheel.delta * 0.05);
            }
        }

        bool isPointer = false;
        isPointer = dynamic_cast<IconButton*> (ui.hoverElement) != 0;

        input.update(frameTime);
        updateMouseTile();
        handleInput();

        scriptEngine.update();

        window->clear();
		//Draw map
		gameDrawer->drawMapBackground(CM->map, camera);
		gameDrawer->drawMapForeground(CM->map, camera);
		
		//Draw layer properties
		sf::Transform mapScale = sf::Transform().scale(camera->getScale(), camera->getScale());
		for(int i = gameDrawer->startTileY; i < CM->map->height && i < gameDrawer->startTileY + gameDrawer->screenHeight / camera->getScale() + 1; i++){
			for(int j = gameDrawer->startTileX; j < CM->map->width && j < gameDrawer->startTileX + gameDrawer->screenWidth / camera->getScale() + 1; j++){
				if(CM->map->tiles[i * CM->map->width + j].properties){
					sf::Transform mapPosition = mapScale;
                    mapPosition *= sf::Transform().translate((j - gameDrawer->startTileX)*32 - gameDrawer->mapPixelOffsetX,
                            (i - gameDrawer->startTileY)*32 - gameDrawer->mapPixelOffsetY);
                    
					if(CM->map->tiles[i * CM->map->width + j].properties & Tile::t_BlockTop)
						window->draw(*editorSprites->getSprite("blockedtop"), mapPosition);
					if(CM->map->tiles[i * CM->map->width + j].properties & Tile::t_BlockLeft)
						window->draw(*editorSprites->getSprite("blockedleft"), mapPosition);
					if(CM->map->tiles[i * CM->map->width + j].properties & Tile::t_BlockRight)
						window->draw(*editorSprites->getSprite("blockedright"), mapPosition);
					if(CM->map->tiles[i * CM->map->width + j].properties & Tile::t_BlockBottom)
						window->draw(*editorSprites->getSprite("blockedbottom"), mapPosition);
				}
			}
		}
		//Draw marquee selection
        if(marqueeExists){
            drawMarquee();
        }
        
        
        //Draw temp region if its being created
        if(regionWidth != 0 && regionHeight != 0){
            sf::Transform mapPosition = mapScale;
            mapPosition *= sf::Transform().translate(regionX - (gameDrawer->startTileX)*32 - gameDrawer->mapPixelOffsetX,
				regionY - (gameDrawer->startTileY)*32 - gameDrawer->mapPixelOffsetY);
            sf::RectangleShape regionRect;
            regionRect.setSize(sf::Vector2f(regionWidth, regionHeight));
            regionRect.setFillColor(sf::Color(100, 100, 100, 60));
            window->draw(regionRect, mapPosition);
        }
        //Draw regions
        for(int i = 0; i < CM->regionManager->size; i++){
            if(CM->regionManager->regions[i]){
                EditorRegion* region = (EditorRegion*)CM->regionManager->regions[i];
                sf::Transform mapPosition = mapScale;
                mapPosition *= sf::Transform().translate(region->x - (gameDrawer->startTileX)*32 - gameDrawer->mapPixelOffsetX,
                    region->y - (gameDrawer->startTileY)*32 - gameDrawer->mapPixelOffsetY);
                window->draw(*region, mapPosition);
            }
        }
        
        //Draw UI
		window->draw(ui);

        Label lbl(2, 2, 12, sf::Color::White, "FPS: " + StringHelper::itos(fpsCounter->getCounter()));
        window->draw(lbl);

        //Draw cursor at mouse position.
        sf::Transform cursorPos;
        cursorPos.translate(sf::Mouse::getPosition(*window).x, sf::Mouse::getPosition(*window).y);
        if(isPointer){
            cursor = PointerCursor;
            cursorPos.translate(-10, 0); //Place end of pointer at mouse.
            window->draw(*editorSprites->getSprite("pointercursor"), cursorPos);
        }else if(spritePanel->visible || mouseOverUI){
            cursor = Arrow;
            window->draw(*editorSprites->getSprite("cursor"), cursorPos);
        }else if(CM->selectedTool == EditorInstance::Move && isMouseInMarquee){
            cursor = MoveCursor;
            cursorPos.translate(-12, -12); //Center the move cursor
            window->draw(*editorSprites->getSprite("movecursor"), cursorPos);
        }else if(CM->selectedTool == EditorInstance::Marquee && !isMouseInMarquee){
            cursor = SelectCursor;
            window->draw(*editorSprites->getSprite("selectcursor"), cursorPos);
        }else if(CM->selectedTool == EditorInstance::Marquee && isMouseInMarquee){
            cursor = MoveCursor;
            cursorPos.translate(-12, -12); //Center the move cursor
            window->draw(*editorSprites->getSprite("movecursor"), cursorPos);
        }else if(CM->selectedTool == EditorInstance::Fill){
            cursor = FillCursor;
            cursorPos.translate(-12, -12); //Center can on cursor.
            window->draw(*editorSprites->getSprite("fill"), cursorPos);
        }else{
            cursor = Arrow;
            window->draw(*editorSprites->getSprite("cursor"), cursorPos);
        }

        window->display();
        gameSprites->update(frameTime);
        editorSprites->update(frameTime);

        fpsCounter->increment(frameTime);
        sf::sleep(sf::milliseconds(1));
    }
}

/** 
* UI event trapper.
*/
bool Editor::trapped(InterfaceEvent* event){
    if(event->type == InterfaceEvent::LeftClick){
        if(((InterfaceElement*) event->source)->parent == &infoPanel){
            showSpritePanel();
        }else if(((InterfaceElement*) event->source)->parent == spritePanel){
            spriteSelected(((IconButton*) event->source)->icon);
        }else if(event->source == &brushButton){
            selectTool(EditorInstance::Brush);
        }else if(event->source == &marqueeButton){
            selectTool(EditorInstance::Marquee);
        }else if(event->source == &fillButton){
            selectTool(EditorInstance::Fill);
        }else if(event->source == &moveButton){
            selectTool(EditorInstance::Move);
        }else if(event->source == &regionButton){
            selectTool(EditorInstance::Region);
        }else if(event->source == &saveButton){
            saveMap();
        }else if(event->source == &newMapButton){
            newMap();
        }else if(event->source == &openMapButton){
            showOpenWindow();
        }else if(dynamic_cast<MapTab*> (((InterfaceElement*) event->source)) != 0){
            selectEditorInstance(((MapTab*)event->source)->instance);
        }else if(dynamic_cast<MapTab*> (((InterfaceElement*) event->source)->parent) != 0){
            mapTabClose((InterfaceElement*) event->source);
        }
    }else if(event->type == InterfaceEvent::DoubleClick){
        if(dynamic_cast<Label*> ((InterfaceElement*) event->source) != 0 &&
                ((InterfaceElement*) event->source)->parent != 0 && ((InterfaceElement*) event->source)->parent->parent != 0 &&
                dynamic_cast<FileDialog*> (((InterfaceElement*) event->source)->parent->parent->parent) != 0){
            openMap(((Label*) event->source)->getString());
            ui.hoverElement = 0;
        }
    }else if(event->type == InterfaceEvent::Custom){
        if(event->source == &regionWindow){
            updateRegion();
        }else if(event->source == &regionPanel){
            if(CM->selectedTool == EditorInstance::Region){
                selectedRegion = (EditorRegion*)event->args;
                updateRegion();
            }
        }
    }
    return true;
}

void Editor::drawMarquee(){

	//Calculate marquee positions
    int xTile = camera->getLeftTile();
    int yTile = camera->getTopTile();

    int xOffset = ((int)(CM->cameraPosition->x)) % 32;
    int yOffset = ((int)(CM->cameraPosition->y)) % 32;

    int width = std::abs(CM->marqueeTool.width);
    int height = std::abs(CM->marqueeTool.height);

    //If we are moving a selection then redraw the tiles being moved at sprite location.
    if (marqueeMove && CM->selectedTool == EditorInstance::Move) {
        sf::RectangleShape moveFill;
        moveFill.setSize(sf::Vector2f(width * 32, height * 32));
        sf::Transform moveTransform = sf::Transform().scale(camera->getScale(), camera->getScale());
        sf::Transform fillTransform = moveTransform;

        //Draw black fill over map.
        fillTransform *= sf::Transform().translate((CM->moveSource.left - xTile)*32 - xOffset
                , (CM->moveSource.top - yTile)*32 - yOffset);
        moveFill.setFillColor(sf::Color::Black);

        window->draw(moveFill, fillTransform);

        //Draw all tiles in selection.
        for (int l = 0; l < LAYERS; l++) {
            for (int i = 0; i < CM->moveSource.height; i++) {
                for (int j = 0; j < CM->moveSource.width; j++) {
                    //Check that tile is in map.
                    if(CM->moveSource.left + j >= 0 && CM->moveSource.left + j < CM->map->width &&
                            CM->moveSource.top + i >= 0 && CM->moveSource.top + i < CM->map->height){
                        
                        sf::Transform position = moveTransform;
                        position *= sf::Transform().translate((CM->marqueeTool.left - xTile + j)*32 - xOffset,
                                (CM->marqueeTool.top - yTile + i)*32 - yOffset);
                        int drawTile = CM->moveSource.left + j + (CM->moveSource.top + i) * CM->map->width;
                        if (CM->map->tiles[drawTile][l] != NO_SPRITE)
                            window->draw(*gameSprites->getSprite(CM->map->tiles[drawTile][l]), position);
                    }
                }
            }
        }

    }

    //Draw marquee tool.
    sf::RectangleShape marqueeDraw;
    marqueeDraw.setFillColor(sf::Color::Transparent);
    marqueeDraw.setOutlineThickness(1);
    marqueeDraw.setOutlineColor(sf::Color::Black);
    
    marqueeDraw.setSize(sf::Vector2f(width*32, height*32));

	//If marquee tool sizes are negative then move marquee so they become positive.
    int left = CM->marqueeTool.left;
    if(CM->marqueeTool.width < 0) left = left - width;
    int top = CM->marqueeTool.top;
    if(CM->marqueeTool.height < 0) top = top - height;
    
    sf::Transform marqueeTrans = sf::Transform().scale(camera->getScale(), camera->getScale());
    marqueeDraw.setPosition((left - xTile)*32 - xOffset, (top - yTile)*32 - yOffset);
    window->draw(marqueeDraw, marqueeTrans);

    //Draw animated selection border on top and bottom
    for(int i = 0; i < width; i++){
        sf::Transform positionTop = marqueeTrans;
        positionTop *= sf::Transform().translate((left - xTile + i)*32 - xOffset, (top - yTile)*32 - 1 - yOffset);
        window->draw(*editorSprites->getSprite("marquee_selection_horizontal"), positionTop);

        sf::Transform positionBottom = marqueeTrans;
        positionBottom *= sf::Transform().translate((left - xTile + i)*32 - xOffset, (top - yTile + height)*32 - yOffset);
        window->draw(*editorSprites->getSprite("marquee_selection_horizontal"), positionBottom);
    }
    //Draw animated selection border on left and right sides
    for(int i = 0; i < height; i++){
        sf::Transform positionLeft = marqueeTrans;
        positionLeft *= sf::Transform().translate((left - xTile)*32 - xOffset, (top - yTile + i)*32 - yOffset);
        positionLeft.rotate(90);
        window->draw(*editorSprites->getSprite("marquee_selection_horizontal"), positionLeft);

        sf::Transform positionRight = marqueeTrans;
        positionRight *= sf::Transform().translate((left - xTile + width)*32 + 1 - xOffset, (top - yTile + i)*32 - yOffset);
        positionRight.rotate(90);
        window->draw(*editorSprites->getSprite("marquee_selection_horizontal"), positionRight);
    }

    
    //Draw highlight
    for(int i = 0; i < height; i++){
        for(int j = 0; j < width; j++){
            sf::Transform position = marqueeTrans;
            position *= sf::Transform().translate((left - xTile + j)*32 - xOffset, (top - yTile + i)*32 - yOffset);
            window->draw(*editorSprites->getSprite("marquee_selection_highlight"), position);
        }
    }
}

/** 
* Updates the current map tile and the tile the mouse is currently on.
*/
void Editor::updateMouseTile(){
    float cameraX = ((float)camera->getX())/32.0;
    float cameraY = ((float)camera->getY())/32.0;
    
    float tmapTileX = cameraX;
    float tmapTileY = cameraY;
    tmapTileX -= (camera->getWidth() / 32 + 1)/2;
    tmapTileY -= (camera->getHeight() / 32 + 2)/2;
    
    //Copy previous mouse positions
    lastMouseTileX = mouseTileX;
    lastMouseTileY = mouseTileY;
    lastMouseX = mouseX;
    lastMouseY = mouseY;

    //Get new mouse positions
    mouseX = sf::Mouse::getPosition(*window).x;
    mouseY = sf::Mouse::getPosition(*window).y;
    
    float tmouseTileX = ((float)mouseX)/32.00/camera->getScale();
    float tmouseTileY = ((float)mouseY)/32.00/camera->getScale();
    tmouseTileX += tmapTileX;
    tmouseTileY += tmapTileY;
    mouseTileX = tmouseTileX;
    mouseTileY = tmouseTileY;
    mapTileX = tmapTileX;
    mapTileY = tmapTileY;
    mouseTile = mouseTileX + mouseTileY* CM->map->width;

    mouseWorldX = (mouseX + (camera->getX() - (camera->getWidth())/2))/camera->getScale();
    mouseWorldY = (mouseY + (camera->getY() - (camera->getHeight() + 48)/2))/camera->getScale();
    
    //Check if mouse in map or marquee.
    marqueeExists = (CM->marqueeTool.width != 0) && (CM->marqueeTool.height != 0);
    isMouseInMarquee = ((mouseTileX) >= CM->marqueeTool.left && (mouseTileX) <= (CM->marqueeTool.width + CM->marqueeTool.left)  &&
        (mouseTileY) >= CM->marqueeTool.top && (mouseTileY) <= (CM->marqueeTool.height + CM->marqueeTool.top));
   
    mouseInMap = (mouseTileX >= 0 && mouseTileY >= 0 && mouseTileX < CM->map->width && mouseTileY < CM->map->height);
}

void Editor::handleInput(){
    //Only process input if window has focus.
    if(!hasFocus)
        return;

    //Check if camera being moved by keyboard.
    if(frameTime - inputTimer > 25 && !ui.hasKeyboard){
        //Check for wasd, if shift is held then move fast.
        //Move up
        if (input.isKeyPressed(sf::Keyboard::W) && CM->cameraPosition->y > 0){
            inputTimer = frameTime;
            if (input.isKeyPressed(sf::Keyboard::LShift) || input.isKeyPressed(sf::Keyboard::RShift))
                CM->cameraPosition->y = CM->cameraPosition->y - 32 * 3;
            else
                CM->cameraPosition->y = CM->cameraPosition->y - 32;
        }
        //Move Down
        if (input.isKeyPressed(sf::Keyboard::S) && CM->cameraPosition->y < CM->map->height * 32){
            inputTimer = frameTime;
            if (input.isKeyPressed(sf::Keyboard::LShift) || input.isKeyPressed(sf::Keyboard::RShift))
                CM->cameraPosition->y = CM->cameraPosition->y + 32 * 3;
            else
                CM->cameraPosition->y = CM->cameraPosition->y + 32;
        }
        //Move Left
        if (input.isKeyPressed(sf::Keyboard::A) && CM->cameraPosition->x > 0){
            inputTimer = frameTime;
            if (input.isKeyPressed(sf::Keyboard::LShift) || input.isKeyPressed(sf::Keyboard::RShift))
                CM->cameraPosition->x = CM->cameraPosition->x - 32 * 3;
            else
                CM->cameraPosition->x = CM->cameraPosition->x - 32;
        }
        //Move Right
        if (input.isKeyPressed(sf::Keyboard::D) && CM->cameraPosition->x < CM->map->width * 32){
            inputTimer = frameTime;
            if (input.isKeyPressed(sf::Keyboard::LShift) || input.isKeyPressed(sf::Keyboard::RShift))
                CM->cameraPosition->x = CM->cameraPosition->x + 32 * 3;
            else
                CM->cameraPosition->x = CM->cameraPosition->x + 32;
        }
    }

    //Update UI, if the mouse is in UI then we are done.
    if((mouseOverUI = ui.update(frameTime, sf::Mouse::getPosition(*window).x, sf::Mouse::getPosition(*window).y,
            input.leftDown, input.rightDown)))
        return;

    //If the UI has keyboard control then we are done.
    if(ui.hasKeyboard)
        return;

    if(scriptEngine.input())
        return;

    //Check if camera being moved by mouse. If a layer key is pressed then do not move map.
    if (!(input.isKeyPressed(sf::Keyboard::Z) || input.isKeyPressed(sf::Keyboard::X)
            || input.isKeyPressed(sf::Keyboard::C) || input.isKeyPressed(sf::Keyboard::V)
            || input.isKeyPressed(sf::Keyboard::B)) &&
            input.rightDown && (mouseX != lastMouseX || mouseY != lastMouseY)) {
        CM->cameraPosition->x += (lastMouseX - mouseX);
        CM->cameraPosition->y += (lastMouseY - mouseY);
    }

    //Pressing escape in sprite panel hides sprite panel.
    if(input.isKeyPressed(sf::Keyboard::Escape) && spritePanel->visible)
        hideSpritePanel();


    //Check if undo or redo are pressed.
    if(input.isKeyPressed(sf::Keyboard::Z) && input.isKeyPressed(sf::Keyboard::LControl)){
        if(undoTimer.passed(frameTime)){
            undoAction();
            mapPanel.setMap(CM->map);
        }
    }else if(input.isKeyPressed(sf::Keyboard::Y) && input.isKeyPressed(sf::Keyboard::LControl)){
        if(undoTimer.passed(frameTime)){
            redoAction();
            mapPanel.setMap(CM->map);
        }
    }else{
        //Clear undo timer if not pressed.
        undoTimer.restart();
    }

    //Check if copy is done on a selection.
    if(marqueeExists && input.isKeyPressed(sf::Keyboard::C) && input.isKeyPressed(sf::Keyboard::LControl))
        clipboardPanel.updateClipboard(CM->map, gameSprites, CM->marqueeTool);
    
    //Check if a deletion is done on a selection. Once delete has been done,
    //do not perform it again until delete has been re-pressed.
    if(!hasCleared && marqueeExists && input.isKeyPressed(sf::Keyboard::Delete)){
        addAction(new ClearAreaAction(CM->map, CM->marqueeTool, false));
        hasCleared = true;
    }else if(!input.isKeyPressed(sf::Keyboard::Delete)){
        hasCleared = false;
    }

    //Check if a cut is being done on a selection, as with delete only perform once per press.
    if(!hasCut && marqueeExists && input.isKeyPressed(sf::Keyboard::X) && input.isKeyPressed(sf::Keyboard::LControl)){
        clipboardPanel.updateClipboard(CM->map, gameSprites, CM->marqueeTool);
        addAction(new ClearAreaAction(CM->map, CM->marqueeTool, true));
        hasCut = true;
    }else if(!input.isKeyPressed(sf::Keyboard::X) || !input.isKeyPressed(sf::Keyboard::LControl))
        hasCut = false;


    //Check for a paste, only done once per press.
    if(!hasPasted && clipboardPanel.clipboard.size() != 0 && input.isKeyPressed(sf::Keyboard::V)
            && input.isKeyPressed(sf::Keyboard::LControl)){
        hasPasted = true;
        PasteAction* paste = new PasteAction(CM->map, &clipboardPanel.clipboard, mouseTileX, mouseTileY);
        addAction(paste);
        if (paste->resizeResult != 0) {
            CM->cameraPosition->x += paste->resizeResult->shiftX * 32;
            CM->cameraPosition->y += paste->resizeResult->shiftY * 32;
            CM->marqueeTool.left += paste->resizeResult->shiftX;
            CM->marqueeTool.top += paste->resizeResult->shiftY;
            mapPanel.setMap(CM->map);
        }
        
    }else if(!(input.isKeyPressed(sf::Keyboard::V) && input.isKeyPressed(sf::Keyboard::LControl))){
        hasPasted = false;
    }

    
    //Check mouse based inputs
    if(sf::Mouse::getPosition(*window).x < window->getView().getSize().x 
        && sf::Mouse::getPosition(*window).y < window->getView().getSize().y
        && sf::Mouse::getPosition(*window).x > 0 && sf::Mouse::getPosition(*window).y > 0){

        if(CM->selectedTool == EditorInstance::Marquee){
            handleMarqueeInput();
        }else if(CM->selectedTool == EditorInstance::Move){ //Move tool
            handleMoveInput();
        }else if(CM->selectedTool == EditorInstance::Brush){ //Brush tool.
            handleBrushInput();
        }else if(CM->selectedTool == EditorInstance::Fill){ //Fill tool
            handleFillInput();
        }else if(CM->selectedTool == EditorInstance::Region){//Region tool.
            handleRegionInput();
        }
    }
    //If the mouse has hovered over a new tile then update the tileinfo panel.
    if(mouseTile != lastMouseTile && mouseInMap){
        lastMouseTile = mouseTile;
        tilePanel.setTile(CM->map->tiles[mouseTile], mouseTileY, mouseTileX);
    }
}


void Editor::handleMoveInput(){
    if(input.leftDown){
        if(input.leftPressed && isMouseInMarquee && marqueeExists){
            CM->moveSource = CM->marqueeTool;
            marqueeMove = true;
        }
        if(marqueeMove){
            updateMarqueeLocation();
        }
    }

    if(input.rightDown){
        if(isMouseInMarquee && marqueeExists){
            CM->marqueeTool.width = CM->marqueeTool.height = 0;
        }
    }
}

void Editor::handleMarqueeInput(){
    if(input.leftDown){
        //If left mouse has been pressed this frame
        if(input.leftPressed){
            //If mouse is inside marquee then begin moving marquee.
            if(isMouseInMarquee){
                marqueeMove = true;
            }else{
                //If mouse is outside marquee then create new one.
                marqueeMove = false;
                CM->marqueeTool.width = CM->marqueeTool.height = 1;
                CM->marqueeTool.left = mouseTileX;
                CM->marqueeTool.top = mouseTileY;
                updateMarqueeLocation();
            }
        }else{
            //If we are moving marquee then move it, otherwise update its size
            if(marqueeMove){
                updateMarqueeLocation();
            }else{
                updateMarqueeSize();
            }
        }
    }
    if(input.rightDown){
        if(isMouseInMarquee && marqueeExists){
            CM->marqueeTool.width = CM->marqueeTool.height = 0;
        }
    }
}

void Editor::handleBrushInput(){
    if(input.leftDown){
        //If there is no marquee, or the mouse is inside it then we can
        //draw with the brush.
        if(!marqueeExists || (isMouseInMarquee && marqueeExists)){

            byte property = 0;
            if(input.isKeyPressed(sf::Keyboard::Num1))
                property = Tile::t_BlockTop;
            if(input.isKeyPressed(sf::Keyboard::Num2))
                property |= Tile::t_BlockRight;
            if(input.isKeyPressed(sf::Keyboard::Num3))
                property |= Tile::t_BlockBottom;
            if(input.isKeyPressed(sf::Keyboard::Num4))
                property |= Tile::t_BlockLeft;

            if(property || (!property && (input.isKeyPressed(sf::Keyboard::RControl) || input.isKeyPressed(sf::Keyboard::LControl)))){
                if(mouseInMap && CM->map->tiles[mouseTile].properties != property){
                    if(!MapResizer::needsResize(CM->map, mouseTileX, mouseTileY)){
                        addAction(new TilePropertyAction(&CM->map->tiles[mouseTile].properties, property, mouseTileX, mouseTileY));
                    }
                }
            }

            //Check which layer we are drawing on.
            int layer = -1;
            if(input.isKeyPressed(sf::Keyboard::Z))
                layer = 0;
            if(input.isKeyPressed(sf::Keyboard::X))
                layer = 1;
            if(input.isKeyPressed(sf::Keyboard::C))
                layer = 2;
            if(input.isKeyPressed(sf::Keyboard::V))
                layer = 3;
            if(input.isKeyPressed(sf::Keyboard::B))
                layer = 4;

            if(layer != -1){
                //Check if target tile is valid for change.
                if((mouseInMap && CM->map->tiles[mouseTile][layer] != infoPanel.selectedSprite->index) || !mouseInMap){
                    bool needsResize = MapResizer::needsResize(CM->map, mouseTileX, mouseTileY);
                    if((needsResize && MapResizer::canResize(CM->map, mouseTileX, mouseTileY))){
                        ChangeTileAction* changeAction = new ChangeTileAction(CM->map, layer, infoPanel.selectedSprite->index, mouseTileX, mouseTileY, infoPanel.selectedSprite->name);
                        CM->cameraPosition->x += changeAction->resizeResult->shiftX * 32;
                        CM->cameraPosition->y += changeAction->resizeResult->shiftY * 32;
                        CM->marqueeTool.left += changeAction->resizeResult->shiftX;
                        CM->marqueeTool.top += changeAction->resizeResult->shiftY;
                        mapPanel.setMap(CM->map);
                        addAction(changeAction);
                    }else if(!needsResize)
                        addAction(new ChangeTileAction(CM->map, layer, infoPanel.selectedSprite->index, mouseTileX, mouseTileY, infoPanel.selectedSprite->name));
                }
            }
        }
    }

    if(input.rightDown){
        //If we right click on a tile and select a layer then clear that tile.
        if(mouseInMap && ((isMouseInMarquee && marqueeExists) || !marqueeExists)){
            if(input.isKeyPressed(sf::Keyboard::Z) && CM->map->tiles[mouseTile][0] != NO_SPRITE)
                addAction(new ClearTileAction(&CM->map->tiles[mouseTile][0], 0, mouseTileX, mouseTileY));
            if(input.isKeyPressed(sf::Keyboard::X) && CM->map->tiles[mouseTile][1] != NO_SPRITE)
                addAction(new ClearTileAction(&CM->map->tiles[mouseTile][1], 1, mouseTileX, mouseTileY));
            if(input.isKeyPressed(sf::Keyboard::C) && CM->map->tiles[mouseTile][2] != NO_SPRITE)
                addAction(new ClearTileAction(&CM->map->tiles[mouseTile][2], 2, mouseTileX, mouseTileY));
            if(input.isKeyPressed(sf::Keyboard::V) && CM->map->tiles[mouseTile][3] != NO_SPRITE)
                addAction(new ClearTileAction(&CM->map->tiles[mouseTile][3], 3, mouseTileX, mouseTileY));
            if(input.isKeyPressed(sf::Keyboard::B) && CM->map->tiles[mouseTile][4] != NO_SPRITE)
                addAction(new ClearTileAction(&CM->map->tiles[mouseTile][4], 4, mouseTileX, mouseTileY));
        }
    }
}

void Editor::handleFillInput(){
    if(input.leftDown){
        //Check which layer we are using fill on.
        int layer = -1;
        if(input.isKeyPressed(sf::Keyboard::Z))
            layer = 0;
        if(input.isKeyPressed(sf::Keyboard::X))
            layer = 1;
        if(input.isKeyPressed(sf::Keyboard::C))
            layer = 2;
        if(input.isKeyPressed(sf::Keyboard::V))
            layer = 3;
        if(input.isKeyPressed(sf::Keyboard::B))
            layer = 4;

        if(layer != -1){
            if(marqueeExists && isMouseInMarquee){
                if(!mouseInMap || (mouseInMap && CM->map->tiles[mouseTile][layer] != infoPanel.selectedSprite->index)){
                    FillAction* fillAction = new FillAction(CM->map, CM->marqueeTool, mouseTileX, mouseTileY,
                            infoPanel.selectedSprite->index, layer, infoPanel.selectedSprite->name);

                    //Check if fill action worked
                    if(fillAction->performed == false)
                        delete fillAction;
                    else{
                        if(fillAction->resizeResult != 0){
                            CM->cameraPosition->x += fillAction->resizeResult->shiftX * 32;
                            CM->cameraPosition->y += fillAction->resizeResult->shiftY * 32;
                            CM->marqueeTool.left += fillAction->resizeResult->shiftX;
                            CM->marqueeTool.top += fillAction->resizeResult->shiftY;
                            mapPanel.setMap(CM->map);
                        }
                        addAction(fillAction);
                    }
                }
            }else if(!marqueeExists && mouseInMap && CM->map->tiles[mouseTile][layer] != infoPanel.selectedSprite->index){
                addAction(new FillAction(CM->map, CM->marqueeTool, mouseTileX, mouseTileY, infoPanel.selectedSprite->index, layer, infoPanel.selectedSprite->name));
            }
        }
    }
}

void Editor::handleRegionInput(){
    
    if(input.isKeyPressed(sf::Keyboard::Delete)){
        if(selectedRegion){
            addAction(new DeleteRegionAction(CM->regionManager, &regionPanel,selectedRegion));
            selectedRegion = 0;
            regionResize = regionResizeX = regionResizeY = regionMove = 0;
            regionWindow.setRegion(0);
            return;
        }
    }
    
    if(input.leftDown){
        //If left mouse has been pressed this frame
        if(input.leftPressed){
            //Get region that the mouse has pressed in.
            selectedRegion = getMouseOverRegion();
            if(selectedRegion == 0){
                regionX = mouseWorldX;
                regionY = mouseWorldY;
            }else{
                regionWindow.setRegion(selectedRegion);
                if(std::abs(mouseWorldX - selectedRegion->x) < 12 ||
                    std::abs(mouseWorldX - (selectedRegion->x + selectedRegion->width)) < 12){
                    regionResizeX = true;
                }
                if(std::abs(mouseWorldY - selectedRegion->y) < 12 ||
                        std::abs(mouseWorldY - (selectedRegion->y + selectedRegion->height)) < 12){
                    regionResizeY = true;
                }
                regionResize = regionResizeX || regionResizeY;
                if(!regionResize){
                    regionMove = true;
                }
            }
        }else{
            if(selectedRegion != 0 && regionResize){

                if(regionResizeX)
                    selectedRegion->width = mouseWorldX - selectedRegion->x;
                if(regionResizeY)
                    selectedRegion->height = mouseWorldY - selectedRegion->y;
				updateRegion();
            }else if(regionMove){
                selectedRegion->x = mouseWorldX - selectedRegion->width / 2;
                selectedRegion->y = mouseWorldY - selectedRegion->height / 2;
				updateRegion();
            }else if(selectedRegion == 0){
                regionWidth = mouseWorldX - regionX;
                regionHeight = mouseWorldY - regionY;
            }
        }
    }else{
        //Check for region create
        if(regionWidth != 0 && regionHeight != 0){
            if(regionWidth < 0){
                regionX += regionWidth;
                regionWidth = std::abs(regionWidth);
            }
            if(regionHeight < 0){
                regionY += regionHeight;
                regionHeight = std::abs(regionHeight);
            }
            CreateRegionAction* newRegion = new CreateRegionAction(CM->regionManager, &regionPanel, regionX, regionY, regionWidth, regionHeight);
            addAction(newRegion);
            regionWidth = regionHeight = 0;
        }
        if(regionResize){
            if(selectedRegion->width < 0){
                selectedRegion->x += selectedRegion->width;
                selectedRegion->width = std::abs(selectedRegion->width);
				updateRegion();
            }
            if(selectedRegion->height < 0){
                selectedRegion->y += selectedRegion->height;
                selectedRegion->height = std::abs(selectedRegion->height);
				updateRegion();
            }
        }
        regionResize = regionResizeX = regionResizeY = regionMove = 0;
    }
}

void Editor::updateMarqueeSize(){
    CM->marqueeTool.width = mouseTileX - CM->marqueeTool.left;
    CM->marqueeTool.height = mouseTileY - CM->marqueeTool.top;
}

void Editor::updateMarqueeLocation(){
    if(marqueeExists){
        CM->marqueeTool.left = (mouseTileX - CM->marqueeTool.width/2);
        CM->marqueeTool.top = (mouseTileY - CM->marqueeTool.height/2);
    }
}

/** 
* Add new map that is being edited. Add new tab.
*/
void Editor::addMapInstance(Map* map, EditorRegionManager* regionManager){
    if(map == 0)
        return;
    
    //Create new instance and set the current one to it.
    EditorInstance* newInstance = new EditorInstance(map);
    instances.push_back(newInstance);
	newInstance->regionManager = regionManager;
    if(CM != 0)
        newInstance->selectedTool = CM->selectedTool;
    CM = newInstance;

    //Create a tab for new instance. Make sure all tabs have the close button
    //enabled now that there is more than one tab.
    MapTab* newTab = new MapTab(CM);
    newTab->closeButton->setIcon(editorSprites->getSprite("cross"));
    newTab->onResize(window->getSize().x, window->getSize().y);
    tabs.push_back(newTab);
    ui.addElement(newTab);
    int x = 0;
    for(unsigned int i = 0; i < tabs.size(); i++){
        tabs.at(i)->rect.left = x;
        x+= tabs.at(i)->rect.width + 10;
    }
}

/** 
* Select the passed editor instance as the current editing instance.
*/
void Editor::selectEditorInstance(EditorInstance* editorInstance){
    
    editorInstance->selectedTool = CM->selectedTool;
    CM = editorInstance;
    camera->setTarget(CM->cameraPosition);
    historyPanel.updateHistory(&CM->actionHistory);
    mapPanel.setMap(CM->map);
    regionPanel.setRegionManager(CM->regionManager);
    if(tabs.size() == 1){
        tabs.at(0)->closeButton->visible = false;
    }else{
        for(unsigned int i = 0; i < tabs.size(); i++){
            tabs.at(i)->closeButton->visible = true;
        }
    }
    ui.onResize(camera->getWidth(), camera->getHeight());
    window->setTitle(CM->map->name);
}

/** 
* Save the map that is being edited.
*/
void Editor::saveMap(){
    MapManager::saveRawMap(CM->map, CM->regionManager, gameSprites);
    MapManager::saveMap(CM->map, CM->regionManager);
}

/** 
* Begin editing new map.
* Creates new editor instance.
*/
void Editor::newMap(){
    addMapInstance(new Map("untitled", 1, 1), new EditorRegionManager());
    selectEditorInstance(CM);
}

/** 
* Open map by name.
*/
void Editor::openMap(std::string mapname){
    if(openFile->selectedFile(mapname)){
		Map* m;
		EditorRegionManager* rm = new EditorRegionManager();
		MapManager::loadRawMap(mapname, &m, rm, gameSprites);
        addMapInstance(m, rm);
        ui.removeElement(openFile);
        openFile->visible = false;
        brushButton.visible = true;
        fillButton.visible = true;
        marqueeButton.visible = true;
        moveButton.visible = true;
        saveButton.visible = true;
        newMapButton.visible = true;
        openMapButton.visible = true;
        selectEditorInstance(CM);
    }
}

/**
 * Display the open file dialog.
 */
void Editor::showOpenWindow(){
    if(!openFile->visible){
        ui.addElement(openFile);
        openFile->onResize(window->getSize().x, window->getSize().y);
        openFile->setPosition(window->getSize().x/2 - openFile->rect.width/2, window->getSize().y/2 - openFile->rect.height/2);
        openFile->visible = true;
    }
}

/** 
* Close a map tab. Removes editor instance and the tab.
*/
void Editor::mapTabClose(InterfaceElement* sender){

    //Find the tabs/instances location in the list.
    EditorInstance* deleteInstance;
    for(unsigned int i = 0; i < tabs.size(); i++){
        if(tabs.at(i)->closeButton == sender){
            deleteInstance = instances.at(i);

            instances.erase(instances.begin() + i);
            ui.removeElement(tabs.at(i));
            tabs.erase(tabs.begin() + i);
        }
    }
    delete deleteInstance;
    selectEditorInstance(instances.at(0));//Select first available instance.

    //Reposition all remaining tabs.
    int x = 0;
    for(unsigned int i = 0; i < tabs.size(); i++){
        tabs.at(i)->rect.left = x;
        x+= tabs.at(i)->rect.width + 10;
    }
}

/** 
* Select passed tool. Changes tool icon button selections.
*/
void Editor::selectTool(EditorInstance::Tool tool){
    //Do nothing if tool already selected.
    if(CM->selectedTool == tool)
        return;

    //Turn off selected animation for currently selected tool.
    if(CM->selectedTool == EditorInstance::Fill){
        fillButton.setIcon(editorSprites->getSprite("fill"));
    }else if(CM->selectedTool == EditorInstance::Brush){
        brushButton.setIcon(editorSprites->getSprite("brush"));
    }else if(CM->selectedTool == EditorInstance::Marquee){
        marqueeButton.setIcon(editorSprites->getSprite("marquee"));
    }else if(CM->selectedTool == EditorInstance::Move){
        moveButton.setIcon(editorSprites->getSprite("move"));
    }else if(CM->selectedTool == EditorInstance::Region){
        regionButton.setIcon(editorSprites->getSprite("region"));
        selectedRegion = 0;
        updateRegion();
    }
    CM->selectedTool = tool;

    //Turn on selected animation for newly selected tool.
    if(tool == EditorInstance::Brush){
        brushButton.setIcon(editorSprites->getSprite("brush_selected"));
    }else if(tool == EditorInstance::Marquee){
        marqueeButton.setIcon(editorSprites->getSprite("marquee_selected"));
    }else if(tool == EditorInstance::Move){
        moveButton.setIcon(editorSprites->getSprite("move_selected"));
    }else if(tool == EditorInstance::Fill){
        fillButton.setIcon(editorSprites->getSprite("fill_selected"));
    }else if(tool == EditorInstance::Region){
        regionButton.setIcon(editorSprites->getSprite("region_selected"));
    }
}

/**
 * Get Region the mouse is hovering over.
 * @return Pointer to region mouse is over, or null.
 */
EditorRegion* Editor::getMouseOverRegion(){
    for(int i = 0; i < CM->regionManager->size; i++){
        EditorRegion* region = (EditorRegion*)CM->regionManager->regions[i];
        if(region){
            if(mouseWorldX >= region->x && mouseWorldX <= (region->x + region->width) &&
                mouseWorldY >= region->y && mouseWorldY <= (region->y + region->height)){
                return region;
            }
        }
    }
    return 0;
}

/**
 * Update region information in RegionPanel and RegionWindow.
 */
void Editor::updateRegion(){
    if(selectedRegion){
        selectedRegion->updateLabel();
    }
    regionWindow.setRegion(selectedRegion);
    regionPanel.redraw();
}

/** 
* Set selected sprite.
*/
void Editor::spriteSelected(BaseSprite* sprite){
    infoPanel.selectedSprite = 0;
    infoPanel.setSelectedSprite(gameSprites->getSprite(sprite->index));
    hideSpritePanel();
}

/** 
* Add a performed action to the actions list.
* @param action The action to add.
*/
void Editor::addAction(BaseAction* action){
    CM->actionHistory.push_front(action);
    if(CM->actionHistory.size() > 100){
        BaseAction* deleteAction = CM->actionHistory.back();
        CM->actionHistory.pop_back();
        delete deleteAction;
    }
    for(unsigned int i = 0; i < CM->redoHistory.size(); i++){
        delete CM->redoHistory.at(i);
    }
    CM->redoHistory.clear();
    historyPanel.updateHistory();
}

/** 
* Undo last action that was done, if any.
*/
void Editor::undoAction(){
    if(CM->actionHistory.size() != 0){
        BaseAction* action = CM->actionHistory.front();
        action->undo();
        CM->redoHistory.push_front(action);
        CM->actionHistory.pop_front();
        historyPanel.updateHistory();
    }
}

/** 
* Redo last action that was undone, if any.
*/
void Editor::redoAction(){
    if(CM->redoHistory.size() != 0){
        BaseAction* action = CM->redoHistory.front();
        action->perform();
        CM->actionHistory.push_front(action);
        CM->redoHistory.pop_front();
        historyPanel.updateHistory();
    }
}

void Editor::showSpritePanel(){
    toolPanel.visible = false;
    spritePanel->visible = true;
}

void Editor::hideSpritePanel(){
    toolPanel.visible = true;
    spritePanel->visible = false;
}

void Editor::bind(lua_State* luaState){

    input.bind(luaState);

    luabind::module(luaState)[
        luabind::class_<Editor>("Editor")
            .def_readonly("input", &Editor::input)
            .def("saveMap", &Editor::saveMap)
            .def("newMap", &Editor::newMap)
            .def("openMap", &Editor::showOpenWindow)
            .def("selectTool", &Editor::selectTool)
            .enum_("constant")[
                luabind::value("Brush", EditorInstance::Brush),
                luabind::value("Fill", EditorInstance::Fill),
				luabind::value("Marquee", EditorInstance::Marquee),
				luabind::value("Region", EditorInstance::Region),
                luabind::value("Move", EditorInstance::Move)
			]
    ];
}

void Editor::push(lua_State* luaState){
    luabind::object o = luabind::object(luaState, this);
    o.push(luaState);
}

Editor::~Editor(){
    for(unsigned int i = 0; i < tabs.size(); i++){
        delete tabs.at(i);
    }
    for(unsigned int i = 0; i < instances.size(); i++){
        delete instances.at(i);
    }
}